package com.bullardev.xldatacluster.providers;

import static com.bullardev.xldatacluster.annotations.StringAttribute.MetaData.NOT_NULL;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import com.bullardev.xldatacluster.annotations.StringAttribute;
import com.bullardev.xldatacluster.dataset.ArgumentTemplate;
import com.bullardev.xldatacluster.dataset.DataSet;
import com.bullardev.xldatacluster.dataset.DataSet.MetaData;
import com.bullardev.xldatacluster.dataset.DataSet.Row;
import com.bullardev.xldatacluster.dataset.DataSet.eDataType;
import com.bullardev.xldatacluster.dataset.DataSetProvider;
import com.bullardev.xldatacluster.dataset.DataSetProviderFactory;
import com.bullardev.xldatacluster.providers.webservice.lists.ListsSoap;

/**
 * This class is a DataSetProviderFactory for SharePoint ResultSets.
 * @author Mubin
 *
 */
public final class SharePointDataSetProvider implements DataSetProviderFactory {

    /** Constant for WSDL URL. */
    public static final String WSDL_URL = "WSDL_URL";

    /** Constant for list name. */
    public static final String LIST_NAME = "LIST_NAME";

    /** Constant for View name. */
    public static final String VIEW_NAME = "VIEW_NAME";

    /** The number of example rows to return. */
    private static final int MAX_EXAMPLE_ROWS = 10;

    /** Long.MAX_VALUE will give error with the web service. */
    public static final long MAX_ROWS = 150;

    /**
     *
     */
    public static final String USER_NAME = "USER_NAME";

    /**
     *
     */
    public static final String PASSWORD = "PASSWORD";

    /**
     *
     */
    public static final String DOMAIN = "DOMAIN";

    /** Singleton instance. */
    private static final SharePointDataSetProvider INSTANCE
                            = new SharePointDataSetProvider();

    /**
     * Default constructor.
     */

    private SharePointDataSetProvider() {
    }

    /**
     * Gets the singleton instance of this class.
     * @return The singleton.
     */
    public static SharePointDataSetProvider getInstance() {
        return INSTANCE;
    }

    @Override
    public DataSetProviderFactoryConfig createConfig() {
        return new ConnectionConfig();
    }

    @Override
    public DataSetProviderFactoryConfig
                createConfig(final Properties props) {
        return new ConnectionConfig(props);
    }

    @Override
    public DataSetProvider createInstance(final
                DataSetProviderFactoryConfig obj) {

        return new InternalDataSetProvider((ConnectionConfig) obj);
    }

    /**
     * Configuration class for the SqlDataSetProvider.
     * @author Mubin
     *
     */
    private final class ConnectionConfig implements
                            DataSetProviderFactoryConfig {


        /** The WSDL URL string. */
        @StringAttribute (displayName   = "WSDL URL",
                          metaData      = {NOT_NULL })
        private String wsdlURL;

        /** The domain used for connecting. */
        @StringAttribute (displayName   = "Domain",
                metaData      = {NOT_NULL })
        private String domain;

        /** The username used in connecting to Sharepoint. */
        @StringAttribute (displayName   = "User Name",
                          metaData      = {NOT_NULL })
        private String username;


        /** The password used in connecting to Sharepoint. */
        @StringAttribute (displayName   = "Password",
                          metaData      = {NOT_NULL })
        private String password;

        /**
         * Default constructor.
         */
        private ConnectionConfig() {
        }

        /**
         * Constructs a config object using the properties provided.
         * @param props The properties to construct the object with.
         */
        private ConnectionConfig(final Properties props) {
            wsdlURL = props.getProperty(WSDL_URL);
            domain = props.getProperty(DOMAIN, "NoDomain");
            username = props.getProperty(USER_NAME);
            password = props.getProperty(PASSWORD);
        }


        /**
         *
         * @return Domain of the connection
         */
        private String getDomain() {
            return domain;
        }

        /**
         * Gets the WSDL URL string.
         * @return The WSDL URL string.
         */
        private String getWsdlURL() {
            return wsdlURL;
        }

        /**
         * Gets the username string.
         * @return The username string.
         */
        private String getUsername() {
            return username;
        }


        /**
         * Gets the password string.
         * @return The password string.
         */
        private String getPassword() {
            return password;
        }


        @Override
        public boolean isValid() {
            return wsdlURL != null && wsdlURL.length() > 0
                    && domain != null && domain.length() > 0
                    && username != null && username.length() > 0
                    && password != null && password.length() > 0;
        }

        @Override
        public Properties getProperties() {
            Properties props = new Properties();
            props.setProperty(WSDL_URL, wsdlURL);
            props.setProperty(USER_NAME, username);
            props.setProperty(PASSWORD, password);
            props.setProperty(DOMAIN, domain);
            return props;
        }
     }

    /**
     * The actual dataset provider for the SQL connection.
     * @author Mubin
     *
     */
    private final class InternalDataSetProvider implements DataSetProvider {

        /** WSDL URL string. */
        private String strWsdlURL;
        /** User Name string. */
        private String strUsername;
        /** Password string. */
        private String strPassword;
        /** Domain string. */
        private String strDomain;
        /** Data List. */

        /**
         * Constructs class using config.
         * @param config The configuration.
         */
        private InternalDataSetProvider(final ConnectionConfig config) {
            if (!config.isValid()) {
                throw new IllegalArgumentException("Config is invalid");
            }
            this.strWsdlURL = config.getWsdlURL();
            this.strUsername = config.getUsername();
            this.strPassword = config.getPassword();
            this.strDomain = config.getDomain();
        }

        @Override
        public DataSet createDataSet(final DataSetProviderConfig config,
                                final Properties args) throws Exception {
            //Long.MAX_VALUE
            return createDataSet(config, MAX_ROWS, args);
        }

        @Override
        public DataSet createExampleDataSet(final DataSetProviderConfig config,
                                       final Properties args) throws Exception {
            return createDataSet(config, MAX_EXAMPLE_ROWS, args);
        }


        /**
         * Creates and returns a DataSet based on the supplied config,
         * max number of rows and arguments.
         * @param config The config to use.
         * @param maxRows The maximum number of rows.
         * @param args The arguments as <code>Properties</code> object.
         * @return A DataSet.
         * @throws Exception On Error
         */
        private DataSet createDataSet(final DataSetProviderConfig config,
                                      final long maxRows,
                                      final Properties args) throws Exception {

            MetaData[] metaData;
            Object[][] data;

            // Safe cast - we know what this is...
            String listName = ((DataSetConfig) config).listName;
            String viewName = ((DataSetConfig) config).viewName;
            boolean includeHiddenFields = ((DataSetConfig) config).
                                                includeHiddenFields;
            SharePointUtility util = SharePointUtility.getInstance();
            SharePointInterface client = util.getSPInstance();
            ListsSoap port = client.sharePointListsAuth(
                    strWsdlURL, (strDomain.equalsIgnoreCase("NoDomain") ?
                                                    "" : strDomain + "\\")
                                                    + strUsername, strPassword);

            //  Extract the data into an ArrayList
            List<Object[]> lst = client.retrieveDataFromList(
                                        port, listName, viewName, maxRows + "",
                                            includeHiddenFields);

            List<String> allColumnNames = client.getColumnNames();
            List<String> allDataTypes = client.getDataTypes();
            //System.out.println(allColumnNames);
            //System.out.println(allDataTypes);
            metaData = new MetaData[allColumnNames.size()];
            for (int i = 0; i < allColumnNames.size(); i++) {
                String thisColDataType = allDataTypes.get(i);
                System.out.println(allColumnNames.get(i)
                                + " --> " + thisColDataType);
                eDataType dataType = eDataType.STRING;
                if (thisColDataType.equalsIgnoreCase("string")) {
                    dataType = eDataType.STRING;
                } else if (thisColDataType.equalsIgnoreCase("boolean")) {
                    dataType = eDataType.BOOLEAN;
                } else if (thisColDataType.equalsIgnoreCase("float")
                        || thisColDataType.equalsIgnoreCase("Number")
                        || thisColDataType.equalsIgnoreCase("Currency")) {
                    dataType = eDataType.DOUBLE;
                } else if (thisColDataType.equalsIgnoreCase("datetime")) {
                    dataType = eDataType.DATETIME;
                }

                String columnName = (String) allColumnNames.get(i);
                metaData[i] = new MetaData(columnName, columnName,
                                                            dataType);
            }


            if (lst == null) {
                lst = new ArrayList<Object[]>();
            }
            data = lst.toArray(new Object[][]{});

            //System.out.println("Data: " + data.length);
            //System.out.println("MetaData: " + metaData.length);
            return new DataSet(metaData, data);


        }


        @Override
        public String testConnection() throws Exception {
            ListsSoap port = SharePointUtility.getInstance().getSPInstance().
                    sharePointListsAuth(strWsdlURL, strDomain
                                        + "\\" + strUsername, strPassword);
            if (port == null) {
                return "Failure";
            } else {
                return "success";
            }
        }

        @Override
        public DataSetProviderConfig createConfig() {
            return new DataSetConfig();
        }

        @Override
        public DataSetProviderConfig createConfig(final Properties props) {
            return new DataSetConfig(props);
        }

        /**
         * The config object for creating the DataSet.
         * @author Mubin
         *
         */
        private final class DataSetConfig implements DataSetProviderConfig {
            /**List Name to be read. */
            @StringAttribute (displayName   = "List Name",
                              metaData      = {NOT_NULL })
            private String listName;

            /**View Name of the corresponding list. */
            @StringAttribute (displayName   = "View Name",
                    metaData      = {NOT_NULL })
            private String viewName;

            /** Include hidden fields?. **/
            private boolean includeHiddenFields;

            @Override
            public boolean isValid() {
                return listName != null && listName.length() > 0;
            }

            @Override
            public List<ArgumentTemplate> getArgumentTemplate() {
                List<ArgumentTemplate> result =
                        new ArrayList<ArgumentTemplate>();
                return result;
            }

            /**
             * Default constructor.
             */
            private DataSetConfig() {
            }

            /**
             * Constructs a config object using the properties provided.
             * @param props The properties to construct the object with.
             */
            private DataSetConfig(final Properties props) {
                listName = props.getProperty(LIST_NAME);
                viewName = props.getProperty(VIEW_NAME);
                String includeHiddenFieldsStr = props.
                        getProperty("HIDDEN_FIELDS", "false");
                if ("true".equalsIgnoreCase(includeHiddenFieldsStr)) {
                    includeHiddenFields = true;
                } else {
                    includeHiddenFields = false;
                }
            }

            @Override
            public Properties getProperties() {
                Properties props = new Properties();
                props.setProperty(LIST_NAME, listName);
                props.setProperty(VIEW_NAME, viewName);
                props.setProperty("HIDDEN_FIELDS", includeHiddenFields + "");
                return props;
            }

        }
    }

}
